﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: CountingFunctions.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-09-06 11:39 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Linq;

namespace BOL.Maths.Functions
{
    /// <summary>
    /// Provides constants and static methods for series.
    /// </summary>
    public static class CountingFunctions
    {
        /// <summary>
        /// Returns n!.
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public static double Factorial(int n)
        {
            if (n < 0)
                throw new ArgumentOutOfRangeException("n", "n should be a positive integer.");
            
            if (n == 0 || n == 1)
                return 1;

            return n < 170 ? n * Factorial(--n) : Math.Exp(DistributionFunctions.LogGamma(n + 1));
        }

        /// <summary>
        /// Returns permutation, n! / (n - r)!.
        /// </summary>
        /// <param name="n">the number of elements available for selection.</param>
        /// <param name="r">the number of elements to be selected [0, n].</param>
        /// <returns></returns>
        public static double Permutaion(int n, int r)
        {
            if (n < 0 || r < 0)
                throw new ArgumentException("n and r should be a positive integer.");
            
            if (n < r)
                throw new ArgumentException("r should be smaller than n or equal.");
            
            return Factorial(n) / Factorial(n - r);
        }

        /// <summary>
        /// Returns combination, n! / (k! * (n - k)!).
        /// </summary>
        /// <param name="n">the number of elements available for selection.</param>
        /// <param name="k">the number of elements to be selected [0, n].</param>
        /// <returns></returns>
        public static double Combination(int n, int k)
        {
            if (n < 0 || k < 0)
                throw new ArgumentException("n and k should be a positive integer.");
            
            if (n < k)
                throw new ArgumentException("k should be smaller than n or equal.");

            return n < 170 ? Factorial(n) / (Factorial(k) * Factorial(n - k)) :
                Math.Exp(DistributionFunctions.LogGamma(n + 1) - DistributionFunctions.LogGamma(k + 1) - DistributionFunctions.LogGamma(n - k + 1));
        }

        /// <summary>
        /// Returns binomial coefficient. Same to combination.
        /// </summary>
        /// <param name="n">the number of elements available for selection.</param>
        /// <param name="k">the number of elements to be selected [0, n].</param>
        /// <returns></returns>
        public static double Binomial(int n, int k)
        {
            return Combination(n, k);
        }

        /// <summary>
        /// Returns mulinomial coefficient. n! / (k1! * k2! * ... * kn!).
        /// </summary>
        /// <param name="k">the countsnumber of elements to be selected [0, n].</param>
        /// <returns></returns>
        public static double Multinomial(int[] k)
        {
            if (k.Any(x => x < 0))
                throw new ArgumentException("k's are expected to be a positive integer.");

            var n = k.Sum();
            var multinomial = Factorial(n);

            return k.Aggregate(multinomial, (current, x) => current / Factorial(x));
        }

        /// <summary>
        /// Returns hypergeometric coefficient, Binormial(m, k) * Binormial(N - m, n - k) / Binormial(N, n). 
        /// </summary>
        /// <param name="N"></param>
        /// <param name="m"></param>
        /// <param name="n"></param>
        /// <param name="k"></param>
        /// <returns></returns>
        public static double Hypergeometric(int N, int m, int n, int k)
        {
            return Combination(m, k) * Combination(N - m, n - k) / Combination(N, n);
        }
    }
}
